package buildinjava.io;

import java.io.File;
import java.io.FileFilter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A filter that selects files, but not directories, based on one or more wildcards.
 * 
 * <code>?</code> matches a single character, excluding the path separator
 * <code>*</code> matches zero or more characters in a filename, excluding the path separator
 * <code>**</code> matches zero or more directories
 *
 * @author Gili Tzabari
 */
public final class WildcardFileFilter implements FileFilter
{
	// package-private because it is referenced by unit tests
	final Pattern pattern;

	/**
	 * Creates a new WildcardFileFilter.
	 *
	 * @param path the path containing wildcards
	 */
	public WildcardFileFilter(final String path)
	{
		final String slashPrefix = "(?<=^|/)";
		final String slashPostfix = "(?=/|$)";
		final String notStarPrefix = "(?<!\\*)";
		final String notStarPostfix = "(?!\\*)";

		String canonicalPath = path.replace("\\", "/");
		if (!canonicalPath.contains("/"))
		{
			// relative path
			canonicalPath = "**/" + canonicalPath;
		}
		final Pattern wildcardTokens = Pattern.compile("(" + slashPrefix + Pattern.quote("**")
																									 + slashPostfix + ")|("
																									 + notStarPrefix + Pattern.quote("*")
																									 + notStarPostfix + ")|("
																									 + Pattern.quote(".") + ")|(" + Pattern.quote("?")
																									 + ")");
		final Matcher matcher = wildcardTokens.matcher(canonicalPath);
		final StringBuilder escaped = new StringBuilder();
		int endOfLastMatch = 0;
		while (matcher.find())
		{
			escaped.append(canonicalPath.substring(endOfLastMatch, matcher.start()));
			if (matcher.group(1) != null)
			{
				assert (matcher.group(1).equals("**"));
				escaped.append(".*");
			}
			else if (matcher.group(2) != null)
			{
				assert (matcher.group(2).equals("*"));
				escaped.append("[^/]*");
			}
			else if (matcher.group(3) != null)
			{
				assert (matcher.group(3).equals("."));
				escaped.append("\\.");
			}
			else if (matcher.group(4) != null)
			{
				assert (matcher.group(4).equals("?"));
				escaped.append(".");
			}
			else
				throw new AssertionError("No groups matched: " + matcher);
			endOfLastMatch = matcher.end();
		}
		escaped.append(canonicalPath.substring(endOfLastMatch));
		this.pattern = Pattern.compile(escaped.toString());
	}

	@Override
	public boolean accept(final File file)
	{
		return pattern.matcher(file.getPath().replace("\\", "/")).matches();
	}
}
